/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-通用能力封装层-公共组件
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.utility.thread.threadpool;

import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ForkJoinPool;

public class ThreadPoolHelper {

    /**
     * spring默认支持的线程池
     * 注意，
     * 线程池使用完毕之后需要手动调用 executor.shutdown();
     */
    public static ThreadPoolTaskExecutor createAsyncExecutor(int corePoolSize, int maxPoolSize, int keepAliveTime, int queueCapacity, ThreadFactory threadFactory) {
        if (threadFactory == null) {
            threadFactory = Executors.defaultThreadFactory();
        }

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(corePoolSize);
        // 设置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        // 设置队列容量
        executor.setQueueCapacity(queueCapacity);
        // 设置线程活跃时间（秒）
        executor.setKeepAliveSeconds(keepAliveTime);
        // 设置默认线程名称
        executor.setThreadNamePrefix("zxbysy-");
        // 设置拒绝策略rejection-policy：当pool已经达到max size的时候，如何处理新任务 CALLER_RUNS：不在新线程中执行任务，而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 设置用于创建新线程的线程工厂
        executor.setThreadFactory(threadFactory);
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程池任务装饰器
        executor.setTaskDecorator(new SpringTaskDecorator());
        executor.initialize();

        return executor;
    }

    /**
     * JDK内置的线程池
     * 注意，
     * 线程池使用完毕之后需要手动调用  threadPoolExecutor.shutdown();
     */
    public static ThreadPoolExecutor createDefaultExecutor(int corePoolSize, int maxPoolSize, long keepAliveTime, int queueCapacity, ThreadFactory threadFactory) {
        if (threadFactory == null) {
            threadFactory = Executors.defaultThreadFactory();
        }

        // 创建线程池用于存储线程任务的队列
        BlockingQueue<Runnable> blockingQueue = queueCapacity > 0
                ? new LinkedBlockingQueue<>(queueCapacity)
                : new SynchronousQueue<>();
        // 创建线程池对象
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // 设置核心线程数
                corePoolSize,
                // 设置最大线程数
                maxPoolSize,
                // 设置线程活跃时间（秒）
                keepAliveTime,
                // 设置线程活跃的时间单位：秒
                TimeUnit.SECONDS,
                // 设置线程池队列
                blockingQueue);
        // 设置拒绝策略rejection-policy：当pool已经达到max size的时候，如何处理新任务 CALLER_RUNS：不在新线程中执行任务，而是有调用者所在的线程来执行
        threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 设置用于创建新线程的线程工厂
        threadPoolExecutor.setThreadFactory(threadFactory);

        return threadPoolExecutor;
    }

    /**
     * 使用fork / join框架可以加速处理大型任务，但要实现这一结果，应遵循一些指导原则：
     * 1、使用尽可能少的线程池 - 在大多数情况下，最好的决定是为每个应用程序或系统使用一个线程池
     * 2、如果不需要特定调整，请使用默认的公共线程池
     * 3、使用合理的阈值将ForkJoinTask拆分为若干个子任务
     * 4、避免在 ForkJoinTask 中出现任何阻塞
     * 建议：
     * 使用invokeAll（）方法向ForkJoinPool提交多个任务通常是个好主意
     * commonPool更适用于CPU密集型操作
     */
    public static ForkJoinPool commonPool() {
        // 提供对公共池的引用，公共池是每个ForkJoinTask的默认线程池。根据Oracle的文档，使用预定义的公共池可以减少资源消耗，因为这会阻止为每个任务创建单独的线程池。
        return ForkJoinPool.commonPool();
    }

    /**
     * 构建自定义ForkJoinPool
     * 说明：
     * ① IO密集型操作，使用ThreadPool而不是ForkJoin、parallelStream
     * ② 避免在没有自定义ForkJoinPool的情况下使用Collection.parallelStream()或Stream.parallel()
     * ③ 共用CommonPool可能导致多个独立的业务操作之间相互争抢线程以至相互影响。
     * ④ 优先避免使用，如果一定要用，请指定特定的ForkJoinPool。
     */
    public static ForkJoinPool buildForkJoinPool() {
        return new ForkJoinPool();
    }

    /**
     * 使用 阿里巴巴TTL线程池，解决线程池场景下ThreadLocal在父子线程之间值传递的问题
     */
    public static Executor createExecutor(@NonNull Executor executor) {
        return TtlExecutors.getTtlExecutor(executor);
    }

    /**
     * 使用 阿里巴巴TTL线程池，解决线程池场景下ThreadLocal在父子线程之间值传递的问题
     */
    public static ExecutorService createExecutorService(@NonNull ExecutorService executorService) {
        return TtlExecutors.getTtlExecutorService(executorService);
    }

}
